/**
 * Module dependencies.
 */

var express = require('express'),
	path = require('path'),
	bisc =  require('bisc'),
	fs = require('fs'),
	_  = require('underscore'),
	os = require('os'),
	sprintf = require('sprintf').sprintf,
	kue = require('kue'),
	jobs = kue.createQueue(),
    nodeBIS = require('node_bis'),
    bis = new bisc(),
    emitter = require('events').EventEmitter,
    optimist = require('optimist'),
    appjs = null,
    processor = null,
    redisServerProcess = null,
    spawn = require('child_process').spawn,
    exec = require('child_process').exec;


// config server with any params ( cli via optimist )
var argv = optimist
                .usage( 'Usage: -p to override port, -d to override sqlite database, -s to perform full startup including processor and redis, -w to run using app.js' )
                .describe( 'p', 'Specify server port ( default: 8888 ).' )
                .describe( 'd', 'Specify path to sqlite file ( default: projectRoot/static/resources/sqlite/store.sqlite ).' )
                .describe( 's', 'Perform a full startup. Will launch Redis server and processor.js ( default: will not automatically start these services ).' )
                .describe( 'w', 'Run in windowed mode ( default: will not launch app.js window ). Be sure to use ./node --harmony app.js -w.' )
                .describe( 'l', 'Attempt to list any available projects contained in the software directory' )
                .describe( 'h', 'Show usage.' )
                .argv;

var init_port = 8888,
    init_defSqliteDir = __dirname + '/static/resources/sqlite/', // assuming node is in the software root
    init_sqliteFile = init_defSqliteDir + 'store.sqlite',
    init_full = false,
    init_windowed = false;

if ( argv.p ) init_port = argv.p;
if ( argv.d ) init_sqliteFile = argv.d;
if ( argv.s ) init_full = true;
if ( argv.w ) init_windowed = true;
if ( argv.h || argv.help ) { optimist.showHelp(); process.exit( 0 ); }
if ( argv.l || argv.list ) {
    fs.exists( init_defSqliteDir, function( exists ) {
        if ( exists ) {
            fs.readdir( init_defSqliteDir, function( err, files ) {
                if ( err ) {
                    console.log( 'Unable to locate', init_defSqliteDir, 'in order to list existing projects.' );
                } else {
                    console.log( 'Available projects:' );
                    console.log( '-------------------' );
                    files.forEach( function( file ) {
                        if ( path.extname( file ) == '.sqlite' ) console.log( path.basename( file, '.sqlite' ) );
                    });
                    console.log( '-------------------' );
                    process.exit( 0 );
                }
            });
        } else {
            console.log( 'Unable to locate', init_defSqliteDir, 'in order to list existing projects.' );
            process.exit( 0 );
        }
    });
}

// check to see if sqlite file is just a name or a full path
if ( path.dirname( init_sqliteFile ) == '.' ) init_sqliteFile = init_defSqliteDir + init_sqliteFile + '.sqlite';

// make sure port is a number
if ( !Number( init_port ) ) init_port = 8888;

// full startup
if ( init_full ) {
    console.log( 'Full startup ( Redis and processor ).' );
    redisServerProcess = exec( /*__dirname + '/extra/redis/redis-server.exe'*/'redis-server' );
    console.log( 'Redis server started.' );
    processor = exec( __dirname + '/node processor.js' );
    console.log( 'Processor started.' );
    // processor listeners
    processor.stdout.on('data', function( data ) {
        console.log( 'Processor data:', data );
    });
    processor.on('exit', function( code, signal ) {
        console.log( 'Processor exited!' );
    });
    processor.on('close', function( code, signal ) {
        console.log( 'Processor closed!' );
    });
    processor.on('disconnect', function( code, signal ) {
        console.log( 'Processor disconnected!' );
    });

    // redis listeners
    redisServerProcess.on('exit', function( code, signal ) {
        console.log( 'redisServerProcess exited!' );
    });
    redisServerProcess.on('close', function( code, signal ) {
        console.log( 'redisServerProcess closed!' );
    });
    redisServerProcess.on('disconnect', function( code, signal ) {
        console.log( 'redisServerProcess disconnected!' );
    });
}

// windowed mode ( using app.js )
if ( init_windowed ) {
    console.log( 'Windowed mode.' );
    //var app = module.exports = express();
    appjs = require('appjs');
}




var app = express.createServer();




// detect os
var winRegExp = new RegExp(/^win\w*\d+$/gi);
var osxRegExp = new RegExp(/darwin/gi);
var linuxRegExp = new RegExp(/linux/gi);
var rootFolder = '/';

// do osx things
if ( os.platform().match( osxRegExp ) ) {
    // handle process exit signals ( UNIX )
    process.on('SIGHUP',function() {
            app.emit('applicationExit', {err: 'sighup'});
            process.exit(0);
    });
    process.on('SIGINT',function() {
				app.emit('applicationExit', {err: 'sigint'});
				process.exit(0);
    });
    // set mac base folder
    rootFolder = '/Volumes';
}

// do windows things
if ( os.platform().match( winRegExp ) ) {
    // set windows base folder
    rootFolder = 'C:';
}





// server configuration
app.configure(function(){
	app.use(express.bodyParser());
	app.use( express.static(__dirname + '/static') );  
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});






function encodeCallback(str,callback) {
	if(typeof callback == 'undefined' || callback == '') {
		return str;
	} else {
		return callback + '(' + str + ')';
	}
}





// jobs ( the server has to do some of these and not the processor because of socket.io and database access )

jobs.process('UpdateImage', function(job, done){
	var dt = new Buffer( job.data.binData, 'binary' ).toString('base64');
	bis.updateThumbnail( job.data.id, dt, function( success ) {
			if ( success ) {
					app.emit( 'updateImageThumbnail', { imageId: job.data.id, thumb: dt } );
			}
  });
	done();
});

jobs.process('BisImageAdded', function(job, done){
    app.emit( 'updateImageUploadedStatus', { imageId: job.data.imageId, online: 1 } );
    app.emit( 'updateStatus', { message: 'Image ' + job.data.imageId + ' uploaded!' } );

	var nxtJob;
	if ( typeof job.data.bisImageId != 'undefined' && job.data.bisImageId != '' ) {
		bis.addUploadedImages({imageId : job.data.imageId, bisId : job.data.bisId, bisImageId : job.data.bisImageId},function(status){done();});

		// uploading imageAttributes if present
		bis.loadBISServer(job.data.bisId,function(bisData){
			if(false != bisData) {
				bis.listImageAttributes({imageId : job.data.imageId, bisId : job.data.bisId},function(attributes){
					if(false != attributes) {
						_.each(attributes,function(attribute){
							nxtJob =  jobs.create('AddBisImageAttribute', {
								imageId: job.data.imageId,
								bisId: job.data.bisId,
								bisImageId: job.data.bisImageId,
								categoryId: attribute.categoryId,
								attributeId: attribute.attributeId,
								config: bis.settings
							}).save();
						
						});
					}
				});
			}
		});
	}
});

jobs.process('ImageFound', function( job, done ) {
    bis.listImages( { bisId: bis.settings.server.id, imageId: job.data.imageId }, function( data, totalCount ) {
        if ( data && data[0] ) {
            app.emit( 'imageAdded', { image: data[0] } );
        }
    });
    done();
});

jobs.process('ImageRemoved', function( job, done ) {
    app.emit( 'imageRemoved', { image: { id: job.data.imageId, folderId: job.data.folderId } } );
    done();
});

jobs.process('BisImageDeleted', function( job, done ) {
	bis.deleteUploadedImages({
        imageId: job.data.imageId,
        bisId: job.data.bisId,
        bisImageId: job.data.bisImageId
	});
	done();
});

jobs.process( 'BisCategoryCreated', function( job, done ) {
    // the db commit is here, because the db can only be opened in one place
    bis.projectDb.run('insert into categories (bisId, categoryId, title) values (?,?,?);', job.data.bisId, job.data.categoryId, job.data.title, function( err ) {
        if ( !err ) {
            app.emit( 'updateStatus', { message: 'Category "' + job.data.title + '" created!' } );
        } else {
            console.log( 'err', err );
        }
    });
    done();
});

jobs.process( 'BisAttributeCreated', function( job, done ) {
    // the db commit is here, because the db can only be opened in one place
    bis.projectDb.run('insert into attributes (bisId, categoryId, attributeId, name) values (?,?,?,?);', job.data.bisId, job.data.categoryId, job.data.attributeId, job.data.name, function( err ) {
        if ( !err ) {
            app.emit( 'updateStatus', { message: 'Attribute "' + job.data.name + '" created!' } );
            app.emit( 'updateCategoryAttributes', { categoryId: job.data.categoryId } );
            if ( job.data.addToImages ) {
                var images = job.data.addToImages;
                for ( var i = 0; i < images.length; i++ ) {
                    // this method takes care of both local and online images
                    job.data.imageId = images[i];
                    var params = {
                        imageId: job.data.imageId,
                        attributeId: job.data.attributeId,
                        categoryId: job.data.categoryId,
                        bisId: job.data.bisId
                    };
                    bis.addImageAttributes( params ); // don't bother with callback here
                }
            }
        } else {
            console.log( 'err', err );
        }
    });
    done();
});

jobs.process('BisImageAttributeAdded', function( job, done ) {
    console.log( 'Attribute', job.data.name, 'successfully associated with image', job.data.imageId );
	bis.addBisImageAttributes({bisId: job.data.bisId, imageId: job.data.imageId, bisImageId: job.data.bisImageId, categoryId: job.data.categoryId, attributeId: job.data.attributeId}, function( st ) {
        if ( st ) {
            app.emit( 'updateStatus', { message: 'Attribute "' + job.data.name + '" added to image' + job.data.imageId + '.' } );
            bis.listImageAttributes( { imageId: job.data.imageId, bisId: job.data.bisId }, function( data ) {
                app.emit( 'updateImageProperties', { imageId: job.data.imageId, data: data } );
            });
        }
    });
	done();
});

jobs.process('BisImageAttributeDeleted', function(job, done){
	bis.deleteBisImageAttributes({bisId: job.data.bisId, imageId: job.data.imageId, bisImageId: job.data.bisImageId, categoryId: job.data.categoryId, attributeId: job.data.attributeId}, function( st ){
        if ( st ) {
            app.emit( 'updateStatus', { message: 'Attribute "' + job.data.name + '" deleted from image' + job.data.imageId + '.' } );
            bis.listImageAttributes( { imageId: job.data.imageId, bisId: job.data.bisId }, function( data ) {
                app.emit( 'updateImageProperties', { imageId: job.data.imageId, data: data } );
            });
        }
    });
	done();
});

jobs.process('BISImageUploadError', function( job, done ) {
    bis.projectDb.run('update image set error = "'+ job.data.message +'", blackList = 1 where id = '+ job.data.imageId +';', function( err, row ) {
        if ( err ) {
            console.log( 'error updating error', err );
        }
    });
    done();
});

// Configuration
app.configure(function(){
	app.use(express.bodyParser());
	app.use(express.static(__dirname + '/static'));  
});

// routes


// images

app.all('/images/add', function(request, response){
	var callback = request.query['callback'] || request.param('callback') || '';
	var bisId = request.query['bisId'] || request.param('bisId') || '';
	var types = request.query['types'] || request.param('types') || [];
	var job;
	
	bis.loadBISServer( bisId, function( bisData ) {
		if ( !bisData ) {
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(encodeCallback(JSON.stringify({ success: false, error: {code: 103, message: 'Not a valid bis server id'}}),callback));
		} else {
			var fn = function( ids ) {
				_.each( ids, function( id ) {
					bis.bisImageUploaded( id, bisId, function( uploadData ) {
						if ( !uploadData ) {
							bis.loadImage( id, true, function( imageData ) {
								if ( imageData && !imageData.blackList ) {
									var folderPath = imageData.folderpath;
									if ( folderPath.charAt( folderPath.length - 1 ) != '/' ) folderPath += '/';
									if ( fs.existsSync( path.normalize(folderPath + imageData.path) ) ) {
										console.log('Uploading image: ' + id);
										job = jobs.create( 'AddBisImage', {
										    imageId: id,
											bisId: bisId,
											folderPath: imageData.folderpath,
											path: imageData.path,
											remotePath: '/' + bisData.key + '/' + imageData.folderId + '/',
											obj: bisN
										}).save();
									} else {
										console.log("File not found to upload: " + path.normalize(folderPath + imageData.path));
									}
								}
							});
						}
					});
				});
			}

			var bisN = new nodeBIS( bisData.key, bisData.url, bisData.path );
			_.each( types, function( type ) {
				type = JSON.parse( type );
				var ids = [];
				if ( type.type == 'images' ) {
					ids = type.ids;
					fn( ids );
				} else if ( type.type == 'folders' ) {
					bis.listImages( {bisId: bisId, folderId: type.ids[0]}, function( data ) {
                        if ( data ) {
                            for ( j = 0; j < data.length; j++ ) {
                                ids.push( data[j].id );
                            }
                            fn( ids );
                        } else {
                            console.log( 'Error listing images for folder(s)', type.ids );
                        }
					});
				}
			});
			response.writeHead( 200, {'Content-Type': 'application/json'} );
			response.end(encodeCallback( JSON.stringify({ success: true}), callback ));
		}
	});
});

app.all('/images/delete', function(request, response){
	var callback = request.query['callback'] || request.param('callback') || '';
	var bisId = request.query['bisId'] || request.param('bisId') || '';
	var types = request.query['types'] || request.param('types') || [];
	var job;
	bis.loadBISServer( bisId, function( bisData ) {
		if ( !bisData ) {
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end( encodeCallback( JSON.stringify({success: false, error: {code : 103, message: bisId + ' is not a valid bisId.'}}), callback));
		} else {
			var fn = function( ids ) {
				_.each( ids, function( id ) {
					bis.bisImageUploaded( id, bisId, function( uploadData ) {
						if ( uploadData ) {
                            console.log('Pulling image: ' + id + ' (bisImageId: ' + uploadData.bisImageId + ')');
                            job = jobs.create( 'DeleteBisImage', {
                                imageId: id,
                                bisImageId: uploadData.bisImageId,
                                bisId: bisId,
                                config: bis.settings
                            }).save();
						}
					});
				});
			}

			_.each( types, function( type ) {
                var type = type;
                try {
                    type = JSON.parse( type );
                } catch ( err ) {
                    // pass
                }
				var ids = [];
				if ( type.type == 'images' ) {
					ids = type.ids;
					fn( ids );
				} else if ( type.type == 'folders' ) {
					bis.listImages( {bisId: bisId, folderId: type.ids[0]}, function( data ) {
                        if ( data ) {
                            for ( j = 0; j < data.length; j++ ) {
                                ids.push( data[j].id );
                            }
                            fn( ids );
                        } else {
                            console.log( 'Error listing images for folder(s)', type.ids );
                        }
					});
				}
			});
			response.writeHead( 200, {'Content-Type': 'application/json'} );
			response.end(encodeCallback( JSON.stringify({ success: true}), callback ));
		}
	});
});

app.all('/images/list', function(req, res) {
    var callback = req.query[ 'callback' ] || req.param( 'callback' );
    var vals = {
        bisId: null,
        imageId: null,
        folderId: null,
        fileType: null,
        blackList: null,
        timestampAdded: null,
        timestampAdded2: null,
        timestampModified: null,
        timestampModified2: null,
        start: null,
        limit: null,
        sort: null,
        dir: null,
        filter: null
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( bis.projectDb && vals.bisId ) {
		switch ( vals.blackList ) {
			case 'true': vals.blackList = 1; break;
			case 'false': vals.blackList = 0; break;
		}

		bis.listImages( vals, function( data, totalCount ) {
            if ( data ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(encodeCallback(JSON.stringify({ success: true, totalCount: totalCount.totalCount, records: data }),callback));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(encodeCallback(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem listing images from the database.' } }),callback));
            }
		});
	} else {
		res.writeHead(200, { 'Content-Type': 'application/json' });
		res.end(encodeCallback(JSON.stringify({ success: false, error: { code: 0, message: 'Listing images requires the bisId field.' } }),callback));
	}
});

app.all('/images/blacklist/:imageId', function(request, response){
  var callback = request.query[ 'callback' ] || request.param( 'callback' );
	var imageId = request.params.imageId;
	if ( imageId ) {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify ({ success : false, error : {code : 102, message : 'Not a valid imageId'}}), callback));
	} else {
		bis.updateBlacklist({id : imageId, status : true},function(){
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(encodeCallback(JSON.stringify ({ success : true }), callback));
		});
	}

});

app.all('/images/whitelist/:imageId', function(request, response){
  var callback = request.query[ 'callback' ] || request.param( 'callback' );
	var imageId = request.params.imageId || '';
	if(imageId == '') {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify ({ success : false, error : {code : 102, message : 'Not a valid imageId'}}), callback));
	} else {
		bis.updateBlacklist({id : imageId, status : false},function(){
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(encodeCallback(JSON.stringify ({ success : true }), callback));
		});
	}

});

// Image types

app.all('/imagetypes/list', function(request, response){
	if(bis.projectDb) {
		var types =  bis.settings.filetypes.split(',');
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(JSON.stringify ({ success : true, data : types}));
	} else {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(JSON.stringify ({ success : false	}));
	}
});

app.all('/imagetypes/add/:type', function(request, response){
	if(bis.projectDb) {
		var types =  [];
		var type = request.params.type || '';
		bis.addImageType(type, function(status) {
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(JSON.stringify ({ success : true}));
		});
	} else {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(JSON.stringify ({ success : false	}));
	}
});

app.all('/imagetypes/delete/:type', function(request, response){
	if(bis.projectDb) {
		var types =  [],pos;
		var type = request.params.type || '';
		bis.deleteImageType(type,function(status){
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(JSON.stringify ({ success : true}));
		});
	} else {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(JSON.stringify ({ success : false	}));
	}
});


// image attributes

app.all('/imageListAttributes', function( req, res ) {
	var callback = req.query['callback'] || req.param('callback');
	var imageId = req.query['imageId'] || req.param('imageId');
	var bisId = req.query['bisId'] || req.param('bisId');
    if ( imageId && bisId ) {
        bis.listImageAttributes({ imageId: imageId, bisId: bisId }, function( data ) {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(encodeCallback(JSON.stringify({ success: true, totalCount: data.length, records: data }),callback));
        });
    } else {
		res.writeHead(200, { 'Content-Type': 'application/json' });
		res.end(encodeCallback(JSON.stringify({ success: false, error: {code: 0, message: 'Listing image attributes requires bisId and imageId fields.'}}),callback));
    }
});

app.all( '/images/attribute/add', function( req, res ) {
	var vals = {
			callback: null,
			imageId: null,
			categoryId: null,
			attributeId: null,
			bisId: null
	};
	for ( var v in vals ) {
			vals[v] = req.query[ v ] || req.param( v );
	}
	if ( vals.categoryId && vals.attributeId && vals.bisId & vals.imageId ) {
		bis.loadBISServer( vals.bisId, function( bisData ) {
			if ( !bisData ) {
				res.writeHead(200, { 'Content-Type': 'application/json' });
				res.end(JSON.stringify({ success: false, error: {code: 103, message: 'Invalid bisId.'}}));
			} else {
				bis.loadImage( vals.imageId, false, function( imageData ) {
					if ( !imageData ) {
						res.writeHead(200, { 'Content-Type': 'application/json' });
						res.end(encodeCallback(JSON.stringify({ success: false, error: {code: 102, message: 'Invalid imageId.'}}), callback));
					} else {
						bis.imageAttributeExists( vals, function( st ) {
							if ( !st ) {
								// attribute does not exist for image
								bis.addImageAttributes( vals, function( addStatus ) {
									if ( addStatus ) {
										res.writeHead(200, { 'Content-Type': 'application/json' });
										res.end( encodeCallback(JSON.stringify({success: true}), callback) );
									} else {
										res.writeHead(200, { 'Content-Type': 'application/json' });
										res.end( encodeCallback(JSON.stringify({ success: false, error: {code: 0, message: 'There was a problem associating the attribute to the image in the database.'}}), callback));
									}
								});
							}
						});
					}
				});
			}
		});
	} else {
		res.writeHead(200, { 'Content-Type': 'application/json' });
		res.end(encodeCallback(JSON.stringify({ success: false, error: {code: 106, message: 'Adding an image attribute requires categoryId, attributeId, bisId, and imageId fields.'}}), callback));
	}
});

app.all('/images/attribute/delete', function(request, response){
	var callback = request.query['callback'] || request.param("callback") || '';
	var imageId = request.query["imageId"] || request.param("imageId") || '';
	var bisImageId = request.query["bisImageId"] || request.param("bisImageId") || '';
	var categoryId = request.query["categoryId"] || request.param("categoryId") || '';
	var valueId = request.query["valueId"] || request.param("valueId") || '';
	var bisId = request.query["bisId"] || request.param("bisId") || '';
	
	if(categoryId == '') {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify ({ success : false, error : {code : 106, message : 'Not a valid categoryId'}}), callback));
	} else if(valueId == '') {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify ({ success : false, error : {code : 107, message : 'Not a valid valueId'}}), callback));
	} else {
		bis.loadBISServer(bisId,function(bisData){
			if(bisData == false) {
				response.writeHead(200, { 'Content-Type': 'application/json' });
				response.end(encodeCallback(JSON.stringify ({ success : false, error : {code : 103, message : 'Not a valid bis server id'}}), callback));
			} else {
				bis.loadImage(imageId,false,function(imageData){
					if(imageData == false) {
						response.writeHead(200, { 'Content-Type': 'application/json' });
						response.end(encodeCallback(JSON.stringify ({ success : false, error : {code : 102, message : 'Not a valid imageId'}}), callback));
					} else {
						bis.imageAttributeExists({imageId : imageId, bisId : bisId, categoryId : categoryId, valueId : valueId}, function( st ) {
							if ( st ) {
								console.log('Attributes exist');
								bis.deleteImageAttributes({imageId : imageId, bisId : bisId, categoryId : categoryId, valueId : valueId},function(delStatus){
									if(delStatus) {
										console.log('Image Attributes Deleted');
										bis.bisImageUploaded(imageId,bisId,function(uploadData){
											if(false != uploadData) {
												console.log('Setting Job');
												// online image
												var job =  jobs.create('DeleteBisImageAttribute', {
													imageId 	: imageId
													, bisId 	: bisId
													, bisImageId: uploadData.bisImageId
													, categoryId: categoryId
													, valueId	: valueId
													, obj		: bisData
												}).save();
											}
										});
									}
								});
							}
						});
					}
				});
			}
		});
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify ({ success : true}),callback));
	}
});

// attributes

app.all( '/attributes/create', function( req, res ) {
    var vals = {
        name: null,
        categoryId: null,
        bisId: null
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( vals.name && vals.categoryId && vals.bisId ) {
        // after 
        bis.addAttribute( vals, function( success ) {
            if ( success ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify ({ success: true}));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify ({ success: false, error: { code: 0, message: 'This attribute already exists.' } }));
            }
        });
    } else {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify ({ success: false, error: { code: 0, message: 'Creating an attribute requires name, attributeId, and bisId fields.' } }));
    }
});

app.all('/attributes/list', function(request, response){
	var callback = request.query['callback'] || request.param("callback") || '';
	var categoryId = request.query["categoryId"] || request.param("categoryId") || '';
	var bisId = request.query['bisId'] || request.param("bisId") || '';
	var params = {};
	if(bisId == '') {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify({ success : false, error : {code : 103, message : 'Not a valid bis server id'}}), callback));
	} else {
        params.bisId = bisId;
        params.categoryId = categoryId;
        params.sort = request.query['sort'] || request.param("sort");
        params.dir = request.query['dir'] || request.param("dir");
        params.filter = request.query['filter'] || request.param("filter");
        params.condition = request.query['condition'] || request.param("condition");
        params.start = request.query['start'] || request.param("start");
        params.limit = request.query['limit'] || request.param("limit");
        
        bis.listAttributes(params,function(data){
            response.writeHead(200, { 'Content-Type': 'application/json' });
            response.end(encodeCallback(JSON.stringify ({ success: true, totalCount: data.length, records: data}),callback));
        });
	}
});

// categories

app.all( '/categories/create', function( req, res ) {
    var vals = {
        title: null,
        description: null,
        elementSet: null,
        term: null,
        bisId: null
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( vals.title && vals.bisId ) {
        // after
        bis.addCategory( vals, function( success ) {
            if ( success ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify ({ success: true}));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify ({ success: false, error: { code: 0, message: 'This category already exists.' } }));
            }
        });
    } else {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify ({ success: false, error: { code: 0, message: 'Creating a category requires title and bisId fields.' } }));
    }
});

app.all('/categories/list', function(request, response){
	var callback = request.query['callback'] || request.param("callback") || '';
	var bisId = request.query['bisId'] || request.param("bisId") || '';
	var params = {};
	if(bisId == '') {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify ({ success : false, error : {code : 103, message : 'Not a valid bis server id'}}), callback));
	} else {
		params.bisId = bisId;
		params.sort = request.query['sort'] || request.param("sort") || '';
		params.dir = request.query['dir'] || request.param("dir") || '';
		params.filter = request.query['filter'] || request.param("filter") || '';
		params.start = request.query['start'] || request.param("start") || '';
		params.limit = request.query['limit'] || request.param("limit") || '';
		
		bis.listCategories(params,function(data){
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(encodeCallback(JSON.stringify ({ success : true, totalCount: data.length, records: data}),callback));
		});
	}
});


// monitors

app.all( '/monitors/update', function( req, res ) {
	var callback = req.query['callback'] || req.param('callback') || '';
    var modified = req.query['monitors'] || req.param('monitors') || '[]';
    bis.updateMonitors( JSON.parse(modified), function( success ) {
        res.writeHead( 200, {'Content-Type': 'application/json'} );
        res.end( encodeCallback(JSON.stringify({success: true}), callback) );
    });
});

app.all('/addMonitorPath', function(request, response){
	var tempPath = request.query["path"] || request.param("path") || '';
	var recursive = request.query["recursive"] || request.param("recursive") || '';
	recursive = (-1 != _.indexOf(['true','false'],recursive)) ? recursive : true;
	// var stats = fs.statSync(tempPath);
	//test to see if dir exists
	if(path.existsSync(tempPath)){
		if(fs.statSync(tempPath).isDirectory()){
			bis.folderExists(tempPath, function(data) {
				if(!data) {
					bis.addPath(tempPath,recursive, function(id){
						if(id !== false){
							bis.loadMonitors();
							response.writeHead(200, { 'Content-Type': 'application/json' });
							response.end(JSON.stringify ({ success : true, data : {id : id}}));
						}else{
							response.writeHead(200, { 'Content-Type': 'application/json' });
							response.end(JSON.stringify ({ success : false}));
						}
						
					});
				} else {
					response.writeHead(200, { 'Content-Type': 'application/json' });
					response.end(JSON.stringify ({ success : false, error : {code : 103, message : 'The Given path is already being monitored'}}));
				}
			});
		}else{
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(JSON.stringify ({ success : false}));
		}
	}else{
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(JSON.stringify ({ success : false}));
	}
});

app.all('/monitors/list', function(request, response){
	var callback = request.query['callback'] || request.param('callback') || '';
	bis.listMonitors( function( data ) {
		data = (data == false) ? [] : data;
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify ({ success : true, totalCount: data.length, data: data}),callback));
	}, true);
});

app.all('/folders/list', function(request, response){
	var callback = request.query['callback'] || request.param('callback') || '';
	var path = request.query['path'] || request.param('path') || rootFolder;
    bis.listDirectory( path, function( results ) {
        response.writeHead(200, { 'Content-Type': 'application/json' });
        var sortFunc = function ( a, b ) {
            // ignores case
            a = a.path.toLowerCase(); b = b.path.toLowerCase();
            if ( a > b ) return 1;
            if (a < b ) return -1;
            return 0;
        }
        response.end( encodeCallback(JSON.stringify({ success : true, records: results.sort(sortFunc) }), callback) );
    });
});

app.all('/monitors/remove/:folderId', function(request, response){
	var callback = request.query['callback'] || request.param('callback');
	var pull = request.query['pull'] || request.param('pull');
	var folderId = request.params.folderId;
	if ( !folderId ) {
		response.writeHead(200, { 'Content-Type': 'application/json' });
		response.end(encodeCallback(JSON.stringify({ success : false, error : {code : 101, message : 'Not a valid folderId'}}), callback));
	} else {
		bis.removeMonitor( folderId, pull, function( data ) {
			bis.loadMonitors();
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(encodeCallback(JSON.stringify({ success: true }),callback));
		});
	}
});

// synchronizing system ( monitors, BIS, etc. )

app.all('/sync', function(request, response){
	// var tempPath = request.query['path'] || '';
	var folderId = request.query['folderId'] || '';
	
	bis.loadFolder(folderId, function(data){
		if(data == false || typeof(data) == 'undefined') {
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(JSON.stringify ({ success : false, error : {code : 101, message : 'Not a valid folderId'}}));
		} else {
			bis.syncCache(data.id, data.path);
			fs.watch(data.path, _.debounce(function(a,b,c){
				if(path.existsSync(data.path)){
					bis.syncCache(data.id, data.path);
				}
			}, 1000));
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(JSON.stringify ({ success : true }));
		}
	});
});

app.all('/updateCache', function( request, response ) {
    var callback = request.query[ 'callback' ] || request.param( 'callback' );
	var bisId = request.query['bisId'] || request.param("bisId") || '';
	var counter1 = 0, counter2 = 0, catAr = [], attAr = [], tmpCat = {},tmpAtt = {}, catId = '';

	bis.loadBISServer( bisId, function( data ) {
		if ( data == false ) {
			response.writeHead(200, { 'Content-Type': 'application/json' });
			response.end(encodeCallback(JSON.stringify({ success: false, error: {code: 103, message: 'Not a valid bis server id'}}),callback));
		} else {
			var attFn = function( catId, bisId, bisN ) {
				var tmpAtt = [], counter2 = 0, attAr = [];
				bisN.listAttributes( { categoryId: catId }, function( attData, err ) {
					if ( attData ) {
						while ( attData.length > 0 ) {
							tmpAtt = attData.pop();
							if ( counter2 > 20 ) {
								bis.projectDb.run('INSERT INTO attributes ("bisId","categoryId","attributeId","name") ' + attAr.join(' UNION '), function( err ) {} );
								counter2 = 0;
								attData = [];
							} else {
								attAr.push(sprintf(" SELECT %s, %s, %s, '%s' ", bisId, catId, tmpAtt.attributeId, tmpAtt.name));
							}
							counter2++;
						}
						if ( attAr.length > 0 ) {
                            bis.projectDb.run( 'INSERT INTO attributes ("bisId","categoryId","attributeId","name") ' + attAr.join(' UNION '), function( err ) {} );
                        }
					}
				});
			};
		
			var bisN = new nodeBIS( data.key, data.url, data.path );
			bisN.listCategories( {}, function( catData, err ) {
				if ( catData ) {
					bis.projectDb.run( 'DELETE FROM categories WHERE bisId = ?;', bisId, function( err ) {
						if ( !err ) {
							bis.projectDb.run( 'DELETE FROM attributes WHERE bisId = ?;', bisId, function( err ) {
								if ( !err ) {
									while ( catData.length > 0 ) {
										tmpCat = catData.pop();
										attFn( tmpCat.categoryId, bisId, bisN );
										if ( counter1 > 20 ) {
											bis.projectDb.run('INSERT INTO categories ("bisId","categoryId","title") ' + catAr.join(' UNION '),function(err){});
											counter1 = 0;
											catAr = [];
										} else {
											catAr.push( sprintf(" SELECT %s, %s, '%s' ", bisId, tmpCat.categoryId, tmpCat.title) );
										}
										counter1++;
									}
									if ( catAr.length > 0 ) bis.projectDb.run( 'INSERT INTO categories ("bisId","categoryId","title") ' + catAr.join(' UNION '), function( err ) {
                                        if ( err ) {
                                            response.writeHead(200, { 'Content-Type': 'application/json' });
                                            response.end(encodeCallback(JSON.stringify({ success: false, error: { code: 0, msg: 'There was a problem inserting categories into the local database.' } }),callback));
                                        } else {
                                            response.writeHead(200, { 'Content-Type': 'application/json' });
                                            response.end(encodeCallback(JSON.stringify({ success: true}),callback));
                                        }
                                    });
								} else {
                                    response.writeHead(200, { 'Content-Type': 'application/json' });
                                    response.end(encodeCallback(JSON.stringify({ success: false, error: { code: 0, msg: 'There was a problem deleting attributes from the local database.' } }),callback));
                                }
							});
						} else {
                            response.writeHead(200, { 'Content-Type': 'application/json' });
                            response.end(encodeCallback(JSON.stringify({ success: false, error: { code: 0, msg: 'There was a problem deleting categories from the local database.' } }),callback));
                        }
					});
				}
			});
		}
	});

});


app.all( '/startup', function( request, response ) {
    var callback = request.query[ 'callback' ] || request.param( 'callback' );

	// sync folders
    bis.syncMonitorCache();

    response.writeHead(200, { 'Content-Type': 'application/json', 'Content-Encoding': 'utf-8' });
    response.end(encodeCallback(JSON.stringify({ success: true}),callback));
});

// filters

app.all( '/listFilters', function( req, res ) {
    var callback = req.query[ 'callback' ] || req.param( 'callback' );
    if ( bis._filters ) {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(encodeCallback(JSON.stringify({ success: true, totalCount: bis._filters.length, records: bis._filters}), callback));
    } else { 
        bis.cacheFilters( function( data ) {
            if ( data ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(encodeCallback(JSON.stringify({ success: true, totalCount: data.length, records: data}), callback));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(encodeCallback(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem loading the filters from the database.' } }), callback));
            }
        });
    }
});


app.all( '/addFilter', function( req, res ) {
    var vals = {
        name: null,
        enabled: 1,
        data: null
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( vals.name && vals.data ) {
        bis.addFilter( vals, function( success ) {
            if ( success ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify ({ success: true}));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify ({ success: false, error: { code: 0, message: 'There was a problem adding the filter to the database.' } }));
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify ({ success: false, error: { code: 0, message: 'Adding a filter requires name and data fields.' } }));
    }
});

app.all( '/updateFilter', function( req, res ) {
    var vals = {
        filterId: null,
        name: null,
        enabled: 1,
        data: null
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( vals.filterId ) {
        bis.updateFilter( vals, function( success ) {
            if ( success ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: true}));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem updating the filter in the database.' } }));
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: false, error: { code: 0, message: 'Updating a filter requires filterId.' } }));
    }
});

app.all( '/removeFilter', function( req, res ) {
	var filterId = req.query[ 'filterId' ] || req.param( 'filterId' );
    if ( filterId ) {
        bis.removeFilter( filterId, function( success ) {
            if ( success ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify ({ success: true}));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem removing the filter from the database.' } }));
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: false, error: { code: 0, message: 'Removing a filter requires the filterId field.' } }));
    }
});

// config options for running a filter ( params signature )
// bis.executeFilter(
//      filterId    number | json   the filter's id or a json string
//      isInDb      boolean         whether or not filterId is a number or json string
//      commit      boolean         whether or not the results should be applied
//      imageIds    array or false  optionally specifiy which images (must exist in db) to be filtered
//      {callback}  function        optionally include a callback( data or false )
// )

app.all( '/testFilter', function( req, res ) {
    // test filter works from post data
    var vals = {
        filterId: null,
        name: null,
        enabled: 0,
        data: null
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( vals.data ) {
        bis.executeFilter( vals, false, false, false, function( data ) {
            if ( data ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: true, totalCount: data.length, data: data }));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem simulating the filter.' } }));
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: false, error: { code: 0, message: 'Simulating a filter requires the selects field.' } }));
    }
});

app.all( '/simulateFilter', function( req, res ) {
    // simulate filter simulates filters already in the database
	var filterId = req.query[ 'filterId' ] || req.param( 'filterId' );
    if ( filterId ) {
        bis.executeFilter( filterId, true, false, false, function( data ) {
            if ( data ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: true, totalCount: data.length, data: data }));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem simulating the filter.' } }));
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: false, error: { code: 0, message: 'Simulating a filter requires the filterId field.' } }));
    }
});

app.all( '/runFilter', function( req, res ) {
    // run filter works from post data
    var vals = {
        filterId: null,
        name: null,
        enabled: 0,
        data: null
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( vals.data ) {
        bis.executeFilter( vals, false, true, false, function( data ) {
            if ( data ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: true, totalCount: data.length, data: data }));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem running the filter.' } }));
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: false, error: { code: 0, message: 'Running a filter requires the selects field.' } }));
    }
});

app.all( '/executeFilter', function( req, res ) {
    // execute filter runs filters already in the database
	var filterId = req.query[ 'filterId' ] || req.param( 'filterId' );
    if ( filterId ) {
        bis.executeFilter( filterId, true, true, false, function( data ) {
            if ( data ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: true, totalCount: data.length, data: data }));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem executing the filter.' } }));
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: false, error: { code: 0, message: 'Executing a filter requires the filterId field.' } }));
    }
});

app.all( '/getSettings', function( req, res ) {
    var callback = req.query['callback'] || req.param('callback');
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(encodeCallback(JSON.stringify({ success: true, data: bis.settings }), callback));
});

// servers

app.all( '/addServer', function( req, res ) {
    var vals = {
        name: null,
        description: null,
        key: null,
        url: null,
        path: null,
        active: 1
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( vals.name && vals.url && vals.path ) {
        bis.addServer( vals, function( success, rowIgnoredOnError ) {
            if ( success ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: true}));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                if ( rowIgnoredOnError ) {
                    res.end(JSON.stringify({ success: false, error: { code: 1, message: 'Cannot add server because hostname conflicts with an existing server.' } }));
                } else {
                    res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem adding the server to the database.' } }));
                }
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify ({ success: false, error: { code: 0, message: 'Adding a server requires name, url, and path fields.' } }));
    }
});

app.all( '/updateServer', function( req, res ) {
    var vals = {
        id: null,
        name: null,
        key: null,
        description: null,
        url: null,
        path: null,
        active: 1
    };
    for ( var v in vals ) {
        vals[v] = req.query[ v ] || req.param( v );
    }
    if ( vals.id ) {
        vals.serverId = vals.id;
        bis.updateServer( vals, function( success ) {
            if ( success ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: true}));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem updating the server in the database.' } }));
            }
        });
    } else { 
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: false, error: { code: 0, message: 'Updating a server requires id.' } }));
    }
});

app.all( '/removeServer', function( req, res ) {
	var serverId = req.query[ 'id' ] || req.param( 'id' );
	if ( !serverId ) serverId = req.query[ 'serverId' ] || req.param( 'serverId' );
    if ( serverId ) {
        bis.removeServer( serverId, function( success ) {
            if ( success ) {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify ({ success: true}));
            } else {
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem removing the server from the database.' } }));
            }
        });
    } else {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ success: false, error: { code: 0, message: 'Removing a server requires the id field.' } }));
    }
});

app.all( '/listServers', function( req, res ) {
	var callback = req.query['callback'] || req.param('callback');
    bis.listServers( function( data ) {
        if ( data ) {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(encodeCallback(JSON.stringify ({ success: true, records: data }),callback));
        } else {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(encodeCallback(JSON.stringify({ success: false, error: { code: 0, message: 'There was a problem removing the server from the database.' } }), callback));
        }
    });
});

app = app.listen( init_port, function() {});
io = require('socket.io').listen(app, { log: false });

bis.openDb( init_sqliteFile, function() {
	bis.loadMonitors();
});

kue.app.listen( 8889 );


// Socket IO Functions
io.sockets.on('connection', function (socket) {
	app.on('imageAdded',function( data ) {
        socket.emit('imageAdded', {success: true, data: data} );
	});
	app.on('imageRemoved',function( data ) {
        socket.emit('imageRemoved', {success: true, data: data} );
	});
	app.on('updateImageProperties',function( data ) {
        socket.emit('updateImageProperties', {success: true, data: data} );
	});
	app.on('updateImageThumbnail',function( data ) {
        socket.emit('updateImageThumbnail', {success: true, data: data} );
	});
	app.on('updateImageUploadedStatus',function( data ) {
        socket.emit('updateImageUploadedStatus', {success: true, data: data} );
	});
	app.on('applicationExit',function( data ) {
        socket.emit('applicationExit', {success: true, data: data} );
	});
	app.on('updateCategoryAttributes',function( data ) {
        socket.emit('updateCategoryAttributes', {success: true, data: data} );
	});
    app.on('updateStatus', function( data ) {
        socket.emit( 'updateStatus', { success: true, data: data } );
    });
});


// windowed mode
if ( init_windowed ) {
    appjs.router.handle = app.handle.bind(app);

    var window = appjs.createWindow({
        width : 800,
        height: 600,
        url: 'http://localhost:8888/'
        //icons : __dirname + '/static/resources/icons'
    });

    // show the window after initialization
    window.on('create', function() {
        window.frame.show();
        window.frame.center();
        //window.frame.openDevTools();
    });

    // add require/process/module to the window global object for debugging from the DevTools
    window.on( 'ready', function() {
        window.require = require;
        window.process = process;
        window.module = module;
        window.addEventListener( 'keydown', function( e ) {
            if ( e.keyIdentifier === 'F12' ) {
                window.frame.openDevTools();
            }
        });
    });
}
